package com.lt.flashcard.model.service;

import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

import org.apache.log4j.Logger;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.apache.shiro.subject.Subject;

import com.lt.flashcard.Utility;
import com.lt.flashcard.model.entity.FlashcardSet;
import com.lt.flashcard.model.entity.Permission;
import com.lt.flashcard.model.entity.Promotion;
import com.lt.flashcard.model.entity.Role;
import com.lt.flashcard.model.entity.User;

/**
 * Authentication and Authorization service.
 * Provide an utility to fetch and save permissions and roles to database
 * @author ocsen
 * @version 1.0
 * @created 15-Aug-2012 21:52:19
 */
public class AAService {

    private static final Logger log = Logger.getLogger(AAService.class);

    /**
     * Get permission list of specific user
     * @param user
     * @return
     */
    public Set<Permission> getPermissionList(User user) {
        Set<Permission> result = new HashSet<Permission>();
        if (user != null) {
            Set<Role> roles = user.getRoles();
            for (Role role : roles) {
                result.addAll(role.getPermissions());
            }
        }
        return result;
    }

    /**
     * Get curent subject
     * @return Subject or null
     */
    public Subject getCurrentSubject() {
        return SecurityUtils.getSubject();
    }

    private User currentUser = null;

    /**
     * Get current user
     * @return User or null
     */
    public User getCurrentUser() {
        Subject subject = getCurrentSubject();
        if (!subject.isAuthenticated()) {
            return null;
        } else {
            // if(currentUser == null) {
            UserService us = Repository.getInstance().getUserService();
            String username = (String) subject.getPrincipal();
            currentUser = us.findByUsername(username);
            // }
            return currentUser;
        }
    }

    /**
     * Do login user with username and password
     * @param username
     * @param password
     * @param remember
     * @return
     */
    public boolean doLogin(String username, String password, boolean remember, boolean redirect) {
        if (Utility.isEmptyString(username) || Utility.isEmptyString(password)) {
            return false;
        }
        String url = Utility.getRememberPage();
        UserService usr = Repository.getInstance().getUserService();
        if (!usr.isActive(username)) {
            return false;
        }

        UsernamePasswordToken token = new UsernamePasswordToken(username, password, remember);
        Subject subject = getCurrentSubject();

        try {
            subject.login(token);
        } catch (Exception e) {
            log.info("Invalid username/password for " + username + ":" + password);
            return false;
        }

        subject.getSession().setAttribute("UserId", getCurrentUser().getId());

        // update login time
        User user = getCurrentUser();

        EcommerceService esrv = Repository.getInstance().getEcommerceService();
        if (user.getLogin() == null) {// this is the first login
            esrv.applyPromotions(user, Promotion.TARGETTYPE_USER, Promotion.CONDITIONTYPE_FIRSTLOGIN, null, true);
        }
        // apply promotion for login at
        esrv.applyPromotions(user, Promotion.TARGETTYPE_USER, Promotion.CONDITIONTYPE_LOGINAT, new Date(), true);

        user.setLogin(new Date());
        usr.save(user);

        if (redirect) {
            if (url != null) {
                Utility.clearRememberPage();
                Utility.rawRedirect(url, true);
            } else {
                Utility.redirect("/dashboard.zul", true);
            }
        }

        return true;
    }

    /**
     * Do logout and terminate the session
     */
    public void doLogout() {
        try {
            getCurrentSubject().logout();
        } catch (Exception e) {
        }
    }

    /**
     * Specify current user has specific role
     * @param role
     * @return
     */
    public boolean hasRole(String role) {
        Subject subject = getCurrentSubject();
        return subject.hasRole(role);
    }

    /**
     * Check to see if user has specific role
     * @param role
     * @param user
     * @return
     */
    public boolean hasRole(String role, User user) {
        for (Role r : user.getRoles()) {
            if (r.getName().equals(role)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Check to see if user has specific role
     * @param roles
     * @param user
     * @return
     */
    public boolean hasRole(Collection<String> roles, User user) {
        return user.getRoles().containsAll(roles);
    }

    /**
     * Check to see if current user has permission
     * @param permission
     * @return
     */
    public boolean hasPermission(String permission) {
        User user = getCurrentUser();
        if (user.getId().equals(1)) {
            return true;
        }

        return getCurrentSubject().isPermitted(permission);
    }

    /**
     * Check to see if current subject is authenticated (login)
     * @return
     */
    public boolean isLogin() {
        Subject subject = getCurrentSubject();
        return subject.isAuthenticated();
    }

    /**
     * Check if user is admin
     * @return
     */
    public boolean isAdmin(User user) {
        return hasRole(Role.ROLE_ADMIN, user);
    }

    /**
     * Check permission to access flaschardset for current user
     * @param flashcardset
     * @return
     */
    public boolean checkAccess(FlashcardSet flashcardset) {
        User user = getCurrentUser();
        if (user == null) {
            return flashcardset.getPrivacy() == FlashcardSet.PRIVACY_PUBLIC;
        }

        if (flashcardset.getAuthor().getId() == user.getId()) {
            return true;
        }

        FlashcardSetService csrv = Repository.getInstance().getFlashcardSetService();

        Long[] deniedUsers = csrv.getDeniedUserIds(flashcardset);
        if (Arrays.asList(deniedUsers).contains(user.getId())) {
            return false;
        }

        Long[] allowedUsers = csrv.getAllowedUserIds(flashcardset);
        if (Arrays.asList(allowedUsers).contains(user.getId())) {
            return true;
        }

        switch (flashcardset.getPrivacy()) {
        case FlashcardSet.PRIVACY_FRIEND:
            UserService usrv = Repository.getInstance().getUserService();
            return usrv.isFriend(user, flashcardset.getAuthor());
        case FlashcardSet.PRIVACY_PRIVATE:
            return user.getId() == flashcardset.getAuthor().getId();
        case FlashcardSet.PRIVACY_PUBLIC:
            return true;
        }

        return false;
    }

    /**
     * Check permission to edit a flashcardset for user
     * @param user
     * @param flashcardset
     * @return
     */
    public boolean checkEdit(User user, FlashcardSet flashcardset) {
        if (user == null || user.getId() == null) {
            return false;
        }

        if (flashcardset == null || flashcardset.getAuthor() == null) {
            return true;
        }

        if (!hasRole(Role.ROLE_ADMIN, user)) {
            if (flashcardset.getAdmin() == FlashcardSet.ADMIN_YES || flashcardset.getAdmin() == FlashcardSet.ADMIN_BUY) {
                return false;
            }
        }

        return flashcardset.getAuthor().getId().equals(getCurrentUser().getId());
    }

    /**
     * Check whether a user can view or access information of target user
     * @param currentUser
     * @param targetUser
     * @param group
     * @return
     */
    public boolean checkProfilePrivacy(User currentUser, User targetUser, int group) {
        if (targetUser == null || targetUser.getId() == null) {
            return false;
        }

        if (currentUser != null && currentUser.getId() != null && currentUser.getId() == targetUser.getId()) {
            return true;
        }

        int privacy = -1;

        switch (group) {
        case User.PRIVACYGROUP_FRIEND:
            privacy = targetUser.getPrivacyFriend();
            break;
        case User.PRIVACYGROUP_JOURNEY:
            privacy = targetUser.getPrivacyJourney();
            break;
        case User.PRIVACYGROUP_MEDAL:
            privacy = targetUser.getPrivacyMedal();
            break;
        case User.PRIVACYGROUP_PROFILE:
            privacy = targetUser.getPrivacyProfile();
            break;
        }

        UserService usrv = Repository.getInstance().getUserService();

        if (privacy == -1) {
            return false;
        }
        if (privacy == User.PRIVACY_PUBLIC) {
            return true;
        }
        if (privacy == User.PRIVACY_FRIEND && usrv.isFriend(currentUser, targetUser)) {
            return true;
        }
        if (privacy == User.PRIVACY_FRIENDSOFFRIENDS && usrv.isFriendOfFriend(currentUser, targetUser)) {
            return true;
        }

        return false;
    }

    /**
     * The algorithm to hash password
     * @param password
     * @return
     */
    public static String hashPassword(String password) {
        Sha256Hash hash = new Sha256Hash(password, null, 1024);
        return hash.toBase64();
    }
}